package com.hk.commons.poi.excel.read.dom;

import com.hk.commons.poi.excel.exception.ExcelReadException;
import com.hk.commons.poi.excel.exception.InvalidCellReadableExcelException;
import com.hk.commons.poi.excel.metadata.*;
import com.hk.commons.poi.excel.read.AbstractExcelReadExecutor;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.PropertyAccessorUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.util.IOUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


/**
 * Dom 解析
 *
 * @author Kevin
 */
@Slf4j
public class DomExcelReadExecutor<T> extends AbstractExcelReadExecutor<T> {

    private Workbook workbook;

    private List<Title> titleList;

    public DomExcelReadExecutor(ReadWorkbook readWorkbook) {
        super(readWorkbook);
    }

    public static String getCellValueString(Cell cell) {
        return new DataFormatter().formatCellValue(cell);
    }

    @Override
    public ReadResult<T> execute() {
        try {
            this.workbook = createWorkBook();
            sheetParseInit();
            return processWorkbook();
        } catch (Exception e) {
            throw new ExcelReadException(e.getMessage(), e);
        } finally {
            IOUtils.closeQuietly(workbook);
        }
    }

    private ReadResult<T> processWorkbook() {
        var result = new ReadResult<T>();
        var sheetMaxIndex = readWorkbook.getMaxSheetIndex();
        var sheetStartIndex = readWorkbook.getStartSheetIndex();
        onStart();
        for (var index = sheetStartIndex; index <= sheetMaxIndex; index++) {
            parseSheet(index, result);
        }
        var errorLogs = onCompleted();
        if (CollectionUtils.isNotEmpty(errorLogs)) {
            result.addErrorLogList(errorLogs);
        }
        return result;
    }

    private void parseSheet(int index, ReadResult<T> result) {
        final var sheet = workbook.getSheetAt(index);
        if (null != sheet) {
            var sheetName = WorkbookUtil.createSafeSheetName(sheet.getSheetName());
            onSheetStart(index, sheetName, sheet.getLastRowNum());
            if (null == result.getTitleList()) {
                result.setTitleList(parseTitleRow(sheet.getRow(readWorkbook.getTitleRow())));
            }
            var sheetData = processSheetData(sheet, index, sheetName);
            result.addSheetData(sheetData);
            if (CollectionUtils.isNotEmpty(sheetData.getErrorLogs())) {
                result.addErrorLogList(sheetData.getErrorLogs());
            }
        }
    }

    /**
     * 解析一个工作薄
     *
     * @param sheet      sheet
     * @param sheetIndex sheetIndex
     * @param sheetName  sheetName
     */
    private SheetData<T> processSheetData(Sheet sheet, int sheetIndex, String sheetName) {
        var lastRowNum = readWorkbook.getSheetMaxRow() > 0 ? Math.min(readWorkbook.getSheetMaxRow(), sheet.getLastRowNum()) : sheet.getLastRowNum();
        var errorLogs = new ArrayList<ErrorLog<T>>();
        var dataSheet = new SheetData<T>(sheetIndex, sheetName);
        T data = null;
        for (var rowIndex = readWorkbook.getSheetStartRow(); rowIndex <= lastRowNum; rowIndex++) {
            var row = sheet.getRow(rowIndex);
            if (null != row) {
                var rowNum = row.getRowNum();
                try {
                    onRowStart(sheetIndex, sheetName, rowNum);
                    data = readRow(row);
                    dataSheet.add(data);
                } catch (InvalidCellReadableExcelException e) {
                    // 有错误的单元格数据，不能设置为属性值，记录日志
                    errorLogs.add(new ErrorLog<>(sheetIndex, sheetName, rowNum, e.getTarget(), e.getInvalidCells()));
                } finally {
                    onRowEnd(sheetIndex, sheetName, rowNum, data, titleList);
                }
            }
        }
        dataSheet.setErrorLogs(errorLogs);
        onSheetEnd(sheetIndex, sheetName);
        return dataSheet;
    }

    /**
     * 解析行
     *
     * @param row row
     */
    @SuppressWarnings("unchecked")
    private T readRow(Row row) {
        final var maxColumnIndex = titleList.size();
        final var wrapper = PropertyAccessorUtils.forBeanPropertyAccess(readWorkbook.getBeanClass());
        var invalidCellList = new ArrayList<InvalidCell>();
        for (var columnIndex = 0; columnIndex < maxColumnIndex; columnIndex++) {
            var cell = row.getCell(columnIndex, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
            var value = Objects.isNull(cell) ? null : getCellValueString(cell);
            try {
                var propertyName = titleList.get(columnIndex).getPropertyName();
                wrapper.setPropertyValue(propertyName, trimToValue(value));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                invalidCellList.add(new InvalidCell(row.getRowNum(), columnIndex, value, titleList.get(columnIndex),
                        STR."\{value} 设置失败,原因:\{e.getMessage()}"));
            }
        }
        if (!invalidCellList.isEmpty()) {
            throw new InvalidCellReadableExcelException("设置属性失败", wrapper.getWrappedInstance(), invalidCellList);
        }
        return (T) wrapper.getWrappedInstance();
    }

    private List<Title> parseTitleRow(Row row) {
        var titles = new ArrayList<Title>(row.getLastCellNum());
        row.forEach(cell -> titles.add(new Title(cell, readWorkbook.getColumnField().get(cell.getColumnIndex()))));
        this.titleList = titles;
        return this.titleList;
    }

    private Workbook createWorkBook() throws IOException {
        if (null != readWorkbook.getFile()) {
            return WorkbookFactory.create(readWorkbook.getFile(), readWorkbook.getPassword());
        }
        if (null != readWorkbook.getInputStream()) {
            return WorkbookFactory.create(readWorkbook.getInputStream(), readWorkbook.getPassword());
        }
        throw new IOException("File and InputStream cannot be null");
    }

    private void sheetParseInit() {
        var maxSheetsIndex = workbook.getNumberOfSheets() - 1;
        if (readWorkbook.isParseAllSheet()) {
            readWorkbook.setStartSheetIndex(0);
            readWorkbook.setMaxSheetIndex(maxSheetsIndex);
        } else {
            readWorkbook.setMaxSheetIndex(Math.min(readWorkbook.getMaxSheetIndex(), maxSheetsIndex));
        }
    }
}
